package com.chen.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;

/**
 * 
* @ClassName: Reflections 
* @Description: 反射工具类，提供调用getter/setter方法，访问私有变量，调用私有方法，获取泛型类型Class，
* 			被AOP过的真实类等工具函数
* @author chen
* @date 2017-4-7 上午10:27:04 
*
 */
public class Reflections {
	
	private static final String GETTER_PREFIX = "get";

	/**
	 * 
	* @Title: invokerGetter 
	* @Description: 调用Getter方法，支持多级，如对象.对象.方法
	* @param @param obj
	* @param @param propertyName
	* @param @return    设定文件  
	* @return Object    返回类型 
	* @throws
	 */
	public static Object invokerGetter(Object obj,String propertyName){
		Object object = obj;
		for(String name : StringUtils.split(propertyName, ",")){
			String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name);//capitalize首字母大写
			object = involeMethod(object,getterMethodName,new Class[]{},new Object[]{});
		}
		return object;
	}

	/**
	 * 
	* @Title: involeMethod 
	* @Description: 直接调用对象方法，无视private/protected修饰符.
	* 				用于一次性调用的情况，否则应使用getAccessibelMethod()函数获得Method后反复调用.
	* 				用于匹配方法名+参数类型
	* @param @param object
	* @param @param getterMethodName
	* @param @param classes
	* @param @param objects
	* @param @return    设定文件 
	* @return Object    返回类型 
	* @throws
	 */
	public static Object involeMethod(Object obj, String methodName,Class<?>[] parameterTypes, Object[] args) {
		Method method = getAccessibleMethod(obj,methodName,parameterTypes);
		if (method == null) {
			throw new IllegalArgumentException("Could not find method ["
					+ methodName + "] on target [" + obj + "]");
		}
		try {
			return method.invoke(obj, args);
		} catch (Exception e) {
			throw convertReflectionExceptionToUnchecked(e);
		}
	}

	/**
	 * 
	* @Title: convertReflectionExceptionToUnchecked 
	* @Description: 将反射时的checked exception转换为unchecked exception
	* @param @param e
	* @param @return    设定文件 
	* @return Exception    返回类型 
	* @throws
	 */
	private static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
		if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
				|| e instanceof NoSuchMethodException) {
			return new IllegalArgumentException(e);
		} else if (e instanceof InvocationTargetException) {
			return new RuntimeException(((InvocationTargetException) e).getTargetException());
		} else if (e instanceof RuntimeException) {
			return (RuntimeException) e;
		}
		return new RuntimeException("Unexpected Checked Exception.", e);
	}

	/**
	 * 
	* @Title: getAccessibleMethod 
	* @Description: 循环向上转型，获取对象的DeclaredMethod,并强制设置为可访问
	* 				如向上转型到Object仍无法找到，返回null.
	* 				匹配函数名+参数类型
	* 用于方法需要被多次调用的情况.先使用本函数先取得Method，然后调用Method.invoke(Object obj,Object... args)
	* @param @param obj
	* @param @param methodName
	* @param @param parameterTypes
	* @param @return    设定文件 
	* @return Method    返回类型 
	* @throws
	 */
	private static Method getAccessibleMethod(Object obj, String methodName,
			Class<?>[] parameterTypes) {
		Validate.notNull(obj,"object can't be null");
		Validate.notBlank(methodName,"methodName can't be blank");
		
		for(Class<?> searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()){
			try {
				Method method = searchType.getDeclaredMethod(methodName, parameterTypes);
				makeAccessible(method);
				return method;
			} catch (Exception e) {
				throw convertReflectionExceptionToUnchecked(e);
			}
		}
		return null;
	}
	
	/**
	 * 
	* @Title: makeAccessible 
	* @Description: 取消Java的权限控制检查,使用private的方法能被调用，不是改变方法的访问权限，尽量不调用实际改动的语句，避免JDK的SecurityManager抱怨
	* @param @param method    设定文件 
	* @return void    返回类型 
	* @throws
	 */
	private static void makeAccessible(Method method) {
		if((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers()))
				&& !method.isAccessible()){
			method.setAccessible(true);
		}
	}

	public static void main(String[] args){
		Student student = new Student();
		student.setId(1);
		student.setName("001");
//		Reflections.getAccessibleMethod(null, "getName", null);
		
		Object val = null;;
		try {
			val = Reflections.invokerGetter(student, "name");//001
			val = Reflections.involeMethod(student, "getName", new Class[] {}, new Object[] {});//001
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println(val + ",");
		
	}
}
